#pragma once

#include <iostream>
#include "Socket.hpp"

using namespace socket_ns;
class SelectServer
{
    const static int gnum = sizeof(fd_set) * 8;
    const static int gdefaultfd = -1;

public:
    SelectServer(uint16_t port) : _port(port), _listensock(std::make_unique<TcpSocket>())
    {
        _listensock->BuildListenSocket(port); // 初始化的时候，通过调用Socket.hpp里面的函数：创建listenfd
    }

    void InitServer()
    {
        for (int i = 0; i < gnum; i++)
        {
            fd_array[i] = gdefaultfd;
        }
        fd_array[0] = _listensock->Sockfd(); // 默认直接添加listensock到数组中
    }

    void Accepter()
    {
        InetAddr addr;
        int sockfd = _listensock->Accepter(&addr);

        if (sockfd > 0)
        {
            LOG(DEBUG, "get a new link,client info:%s:%d\n", addr.Ip().c_str(), addr.Port());
            // 事件处理

            bool flag = false;
            for (int pos = 1; pos < gnum; pos++)
            {
                if (fd_array[pos] == gdefaultfd) // 说明该位置没有被设置！
                {
                    flag = true;
                    fd_array[pos] = sockfd;
                    LOG(INFO, "add success %d to fd_array\n", sockfd);
                    break;
                }
            }
            if (!flag)
            {
                LOG(WARNING, "Server is full\n");
                ::close(sockfd);
            }
        }
    }

    void HandlerIO(int i)
    {
        char buffer[1024];
        ssize_t n = ::recv(fd_array[i], buffer, sizeof(buffer) - 1, 0);

        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << "client say#" << buffer << std::endl;
            // 将客户端说的话，回显给服务器！
            std::string echo_str = "[server echo info]";
            echo_str += buffer;
            ::send(fd_array[i], echo_str.c_str(), echo_str.size(), 0);
        }

        else if (n == 0) // 客户端啥也没说/退出
        {
            LOG(INFO, "client qiut...\n");
            // 关闭fd，让Select不再关心这个FD
            ::close(fd_array[i]);
            fd_array[i] = gdefaultfd;
        }

        else
        {
            LOG(ERROR, "recv error\n");
        }

        return;
    }

    // 一定会存在大量的fd就绪，可能是普通sockfd，也可能是listensockfd
    void HandlerEvent(fd_set &rfds)
    {
        // 事件派发 :遍历辅助数组里面的fd
        for (int i = 0; i < gnum; i++)
        {
            if (fd_array[i] == gdefaultfd)
                continue;
            // 代码走到此时，FD一定是合法的，但是不一定就绪！
            if (FD_ISSET(_listensock->Sockfd(), &rfds)) // 就绪的文件fd可能有多个，所以FD_ISSET，就是用来判断，具体按一个就绪了
            {
                // 1. listensockfd 
                if(_listensock->Sockfd()==fd_array[i])
                {
                    Accepter();
                }

                // 2、普通的fd的读写
                else 
                {
                    HandlerIO(i);
                }
            }
        }
    }

    void PrintDebug()
    {
        std::cout << "fd list:";
        for (int i = 0; i < gnum; i++)
        {
            if (fd_array[i] == gdefaultfd)
                continue;
            std::cout << fd_array[i] << " ";
        }
        std::cout << "\n";
    }

    void Loop()
    {
        // 只要将新的fd加入到辅助数组里面就会由Select统一监管！
        while (true)
        {
            // 1、文件描述符初始化
            fd_set rfds;
            FD_ZERO(&rfds);
            int max_fd = gdefaultfd;

            // 2. 合法的fd 添加到rfds集合中
            for (int i = 0; i < gnum; i++)
            {
                if (fd_array[i] == gdefaultfd)
                    continue;
                FD_SET(fd_array[i], &rfds);

                // 更新出最大的fd值
                if (max_fd < fd_array[i])
                    max_fd = fd_array[i];
            }

            struct timeval timeout{3, 0};

            int n = ::select(_listensock->Sockfd() + 1, &rfds, nullptr, nullptr, &timeout);

            // Select的返回值：n>0就绪事件的个数，n=0,超时；n<0，出错
            switch (n)
            {
            case 0:
                LOG(DEBUG, "time out,%d.%d\n", timeout.tv_sec, timeout.tv_usec);
                break;
            case -1:
                LOG(ERROR, "select error\n");
            default:
                LOG(INFO, "have event ready,n:%d\n", n);
                HandlerEvent(rfds); // 事件派发
                PrintDebug();       // 测试打印
                sleep(1);
                break;
            }
        }
    }

    ~SelectServer()
    {
    }

private:
    uint16_t _port;
    std::unique_ptr<Socket> _listensock;

    // 1. select要正常工作，需要借助一个辅助数组，来保存所有合法fd
    int fd_array[gnum];
};
